<template>
  <transition name="zoom-in-bottom">
    <div class="grid-panel" :style="config.comboPanelStyle" :class="config.comboPanelClass">
      <table
        cellspacing="0"
        cellpadding="0"
        :style="config.comboTableFullWith === false ? null : {width: '100%'}">
        <thead v-if="config.comboHeadVisible" ref="headEle">
          <tr>
            <th v-for="column in config.comboColumns">
              <span :style="getCheckAllPandingLeft(column)">
                <input-checkbox v-if="column.checkboxAll && config.comboMultiSelection" :value="isAllChecked" :field="{}" @input="toggleAllSelection(arguments[0])">
                </input-checkbox>{{column.title}}
              </span>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(node, index) in nodes" ref="nodeEle"
              v-show="getVisibleState(node)"
              :class="{selected: node.selected, active: focusNode === node}"
              @click="toggleSelection(node)" @mouseenter="setFocusNode(node, index)">
            <td v-for="(column, colIndex) in config.comboColumns" :style="{width: column.width}" v-if="!column.hide">
              <div class="cell" :style="[getColumnPaddingLeft(column, colIndex, node), column.cellStyle]" :class="column.cellClass">
                <input-checkbox v-if="column.checkbox" :field="{}" :value="node.selected" @input="toggleSelection(node, arguments[0])">
                </input-checkbox>

                <i class="fa expandBtn"
                   v-if="colIndex === comboExpandColumnIndex && node.children && node.children.length > 0"
                   :class="[node.expanded ? 'fa-minus' : 'fa-plus']"
                   @click.stop="toggleExpand(node)">
                </i><span>{{formatColumnValue(column, node)}}</span>
                <button class="btn btn-xs" v-for="action in column.actions"
                        :class="action.buttonClass"
                        :style="action.buttonStyle" @click.stop.debounce="action.handler && action.handler(node)">
                  {{action.label}}
                </button>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
      <div v-if="isLoading" class="loading-mask">
      </div>
      <div v-if="isLoading" class="loading-spinner">
        <i class="fa fa-spinner fa-pulse fa-fw"></i>
        加载中...
      </div>
    </div>
  </transition>
</template>
<script>
  import Vue from 'vue'
  import _ from 'lodash'
  import InputCheckbox from '../InputCheckbox.vue'

  function initData (items, level, parent, dest, expanded = false) {
    let pId = parent ? parent.$index + '.' : ''
    let newChildrenNodes = []
    _.forEach(items, function (item, index) {
      let node = Object.assign({expanded: expanded, visible: false, $index: pId + (index + 1)},
        _.isObject(item) ? item : null,
        {'parent': parent, 'level': level})
      node.$data = item
      dest.push(node)
      newChildrenNodes.push(node)
      if (node.children && node.children.length > 0) {
        initData(node.children, level + 1, node, dest, node.expanded)
      }
    })
    if (parent) {
      parent.children = newChildrenNodes
    }
  }

  export default {
    components: { InputCheckbox },
    props: {
      dataSource: {
        type: Array
      },
      config: {
        type: Object,
        required: true
      },
      isLoading: {
        type: Boolean
      }
    },
    data () {
      return {
        nodes: [],
        selectedNodes: [],
        focusNodeIndex: -1,
        focusNode: null,
        selectedValues: []
      }
    },
    mounted () {
      this.init(this.dataSource)
    },
    watch: {
      dataSource: function (v) {
        this.init(v)
        this.refreshSelection()
      }
    },
    computed: {
      comboExpandColumnIndex () {
        let comboExpandColumnIndex = -1
        if (this.config.comboExpandColumnIndex !== undefined) {
          comboExpandColumnIndex = this.config.comboExpandColumnIndex
        }
        return comboExpandColumnIndex
      },
      isAllChecked () {
        return this.nodes && this.selectedNodes &&
          this.nodes.length > 0 &&
          this.nodes.length === this.selectedNodes.length
      }
    },
    methods: {
      formatColumnValue (column, node) {
        return column.renderer
          ? column.renderer(node)
          : column.field
            ? node[column.field]
            : column.field === null
              ? '' : node.$data
      },
      init (v) {
        let newNodes = []
        initData(v, 1, null, newNodes)
        this.nodes = newNodes
      },
      getCheckAllPandingLeft (column) {
        return column.checkboxAll && this.config.comboMultiSelection ? {paddingLeft: '24px'} : null
      },
      getColumnPaddingLeft (column, colIndex, row) {
        let padding = colIndex === this.comboExpandColumnIndex
          ? (row.level - 1) * 20 + (row.children && row.children.length > 0 ? 0 : 20) + (+this.config.basePaddingLeft || 0)
          : 0
        if (column.checkbox) {
          padding += 30
        }
        return {paddingLeft: padding + 'px'}
      },
      toggleSelection (node, selected = undefined) {
        selected = selected !== undefined ? selected : !node.selected
        if (!this.config.comboMultiSelection) {
          _.forEach(this.selectedNodes, node => { node.selected = false })
          this.selectedNodes = selected ? [node] : []
          Vue.set(node, 'selected', selected)
        } else {
          this.selectedNodes = selected ? _.concat(this.selectedNodes, node)
            : _.concat([], _.remove(this.selectedNodes, item => item !== node))
          Vue.set(node, 'selected', selected)
        }
        this.$emit('selectionChange', this.selectedNodes)
      },
      getVisibleState (node) {
        return node.level === 1 || (node.parent && node.parent.expanded && node.visible)
      },
      toggleExpand (node) {
        if (node.expanded) {
          this.collapse(node)
        } else {
          this.expand(node)
        }
      },
      expand (node) {
        if (node.children && node.children.length > 0) {
          Vue.set(node, 'expanded', true)
          if (node.children && node.children.length > 0) {
            expand(node, node.children, this)
          }
        }
        function expand (parent, nodes, vm) {
          _.forEach(nodes, function (child) {
            if (parent.level === 1 || (parent.expanded && parent.visible)) {
              child.visible = true
            }
            if (child.children && child.children.length > 0) {
              expand(child, child.children, vm)
            }
          })
        }
      },
      collapse (node) {
        if (node.children && node.children.length > 0) {
          Vue.set(node, 'expanded', false)
          collapse(node.children)
        }

        function collapse (nodes) {
          _.forEach(nodes, function (child) {
            child.visible = false
            if (child.children && child.children.length > 0) {
              collapse(child.children)
            }
          })
        }
      },
      setFocusNode (node, index) {
        this.focusNode = node
        this.focusNodeIndex = index
      },
      setSelectedValues (v) {
        if (this.config.comboMultiSelection) {
          if (_.isEqual(_.sortBy(v), _.sortBy(this.selectedValues))) {
            return
          }
        } else {
          if (_.isEqual(this.selectedValues, v)) {
            return
          }
        }
        this.selectedValues = v
        this.refreshSelection()
      },
      refreshSelection () {
        if (this.nodes && this.nodes.length > 0) {
          let selectedNodes = []
          if (!_.isNil(this.config.comboValueProp)) {
            _.forEach(this.nodes, node => {
              let field = this.config.comboValueProp
              let val = _.get(node, field)
              let selected = _.indexOf(this.selectedValues, val) > -1
              Vue.set(node, 'selected', selected)
              if (selected) {
                selectedNodes.push(node)
                this.expand(node)
              }
            })
          } else {
            _.forEach(this.nodes, node => {
              let val = node.$data
              let selected = _.indexOf(this.selectedValues, val) > -1
              Vue.set(node, 'selected', selected)
              if (selected) {
                selectedNodes.push(node)
                this.expand(node)
              }
            })
          }
          this.selectedNodes = selectedNodes
        } else {
          this.selectedNodes = []
        }
        this.$emit('selectionChange', this.selectedNodes)
      },
      clearFocusState () {
        this.focusNode = null
        this.focusNodeIndex = -1
      },
      quickKey (keyCode, e) {
        let validKey = false
        switch (keyCode) {
          case 39:
            // right
            if (!(this.focusNode && this.focusNode.children && this.focusNode.children.length > 0)) {
              return false
            }
            this.expand(this.focusNode)
            break
          case 37:
            // left
            if (!(this.focusNode && this.focusNode.children && this.focusNode.children.length > 0)) {
              return false
            }
            this.collapse(this.focusNode)
            break
          case 38:
            // up
            if (this.focusNodeIndex > 0 || this.focusNodeIndex === -1) {
              for (let i = (this.focusNodeIndex === -1 ? this.nodes.length : this.focusNodeIndex) - 1; i >= 0; i--) {
                if (this.getVisibleState(this.nodes[i])) {
                  this.focusNode = this.nodes[i]
                  this.focusNodeIndex = i
                  validKey = true
                  this.$el.scrollTop = this.$refs.nodeEle[this.focusNodeIndex].offsetTop -
                    (this.$refs.headEle ? this.$refs.headEle.clientHeight : 0)
                  break
                }
              }
            }
            if (!validKey) {
              this.clearFocusState()
              return false
            }
            break
          case 40:
            // down
            if (this.focusNodeIndex < this.nodes.length - 1) {
              for (let i = this.focusNodeIndex + 1; i < this.nodes.length; i++) {
                if (this.getVisibleState(this.nodes[i])) {
                  this.focusNode = this.nodes[i]
                  this.focusNodeIndex = i
                  validKey = true
                  this.$el.scrollTop = this.$refs.nodeEle[this.focusNodeIndex].offsetTop -
                    (this.$refs.headEle ? this.$refs.headEle.clientHeight : 0)
                  break
                }
              }
            }
            if (!validKey) {
              this.clearFocusState()
              return false
            }
            break
          case 32:
            // blank space do selection
            if (this.focusNode) {
              this.toggleSelection(this.focusNode)
            }
            break
          case 65:
            // select all
            if (this.config.comboMultiSelection && _.findIndex(this.config.comboColumns, col => col.checkboxAll) > -1) {
              if (e.ctrlKey) {
                this.toggleAllSelection(!e.shiftKey)
              }
            }
            break
        }
      },
      toggleAllSelection (allChecked) {
        let checked = allChecked === undefined ? !this.isAllChecked : !!allChecked
        this.nodes && _.forEach(this.nodes, node => {
          node.selected = checked
        })
        this.selectedNodes = checked ? [].concat(this.nodes) : []
        this.$emit('selectionChange', this.selectedNodes)
      }
    }
  }
</script>

<style lang="scss" scoped>
  @import "../../css/variables";

  .grid-panel{
    position: absolute;
    z-index: 3000;
    cursor: default;
    background: #fff;
    border: solid 1px $border-color;
    max-height: 400px;
    overflow: auto;

    .cell {
      position: relative;
      padding: 4px;
    }

    div.checkbox {
      position: absolute;
      left: 4px;
      top: 50%;
      margin-bottom: 0;
      margin-top: -11px;
      height: 20px;
      width: 22px;
    }

    tr {
      position: relative;
      &.selected {
        color: #fff;
        background: lighten($active-color, 10%)
      }
      &.active {
        td:first-child:after {
          content: ' ';
          position: absolute;
          top: 10px;
          bottom: 0;
          left: -6px;
          width: 4px;
          height: 4px;
          border-top: solid 4px transparent;
          border-left: solid 4px $active-color;
          border-bottom: solid 4px transparent;
        }
      }
    }

    td {
      position: relative;
      white-space: nowrap;
    }

    tr:not(:last-child), th {
      border-bottom: solid 1px $border-color;
    }

    th {
      position: relative;
      padding: 4px;
      color: $head-text-color;
      font-weight: normal;
    }

    .expandBtn {
      width: 20px;
      height: 20px;
      line-height: 20px;
      text-align: center;
      cursor: pointer;
    }
    .loading-mask{
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background: $loading-mask-bg-color;
      opacity: .6;
    }
    .loading-spinner {
      position: absolute;
      left: 50%;
      top: 50%;
      font-size: 14px;
      margin-left: -7px;
      margin-top: -7px;
    }
  }
</style>
